﻿namespace System.ComponentModel
{
    using System.Collections.Generic;
    using System.Data.Entity.Core.Mapping;
    using System.Data.Entity.Core.Metadata.Edm;
    using System.Data.Entity.Infrastructure;
    using System.Data.Entity.Utilities;
    using System.Linq;

    internal partial class FunctionDescriptor
    {
        #region Fields

        private readonly string _name;
        private readonly IEnumerable<ParameterDescriptor> _parameters;

        private readonly EntityType _entity;
        private readonly EdmType _storeEntity;

        private EdmFunction _conceptualFunction;
        private EdmFunction _storeFunction;

        #endregion

        private FunctionDescriptor()
        { }

        internal FunctionDescriptor(string name, EntityType entity, DbModel model)
        {
            this._name = name;
            this._entity = entity;

            this._parameters = model.ProviderManifest.CreateParameterDescriptor(
                Utilities.DefaultParameterName,
                typeof(string));

            this._storeEntity = FunctionDescriptor.GetRowType(this._entity, model.ConceptualToStoreMapping);

            CreateConceptualFunction(model.ConceptualModel);

            CreateStoreFunction(model.StoreModel);

            AddFunctionImportMappingComposable(model.ConceptualToStoreMapping);
        }

        private static EdmType GetRowType(EntityType entityType, EntityContainerMapping conceptualToStoreMapping)
        {
            var edmProperties = conceptualToStoreMapping
                .EntitySetMappings.Single(s => s.EntitySet.ElementType == entityType)
                .EntityTypeMappings.Single(t => t.EntityType == entityType)
                .Fragments.Single().PropertyMappings.Cast<ScalarPropertyMapping>()
                .Select(p =>
                    EdmProperty.Create(
                        p.Column.Name,
                        TypeUsage.Create(
                            p.Column.TypeUsage.EdmType,
                            p.Column.TypeUsage.Facets.Where(f => f.Name != "StoreGeneratedPattern"))));

            return RowType.Create(edmProperties, null);
        }

        private void CreateConceptualFunction(EdmModel model)
        {
            FunctionParameter[] returnParameters = new FunctionParameter[]{
                FunctionParameter.Create(
                Utilities.DefaultReturnValueName,
                this._entity.GetCollectionType(),
                ParameterMode.ReturnValue)};

            EntitySet[] entitySets = new EntitySet[]{ 
                model.Container.EntitySets
                    .Single(s => s.ElementType == this._entity)
            };

            var functionPayload =
                new EdmFunctionPayload
                {
                    Parameters = this._parameters
                        .Select(
                            p => FunctionParameter.Create(
                                p.Name,
                                p.EdmType,
                                ParameterMode.In)).ToArray(),
                    ReturnParameters = returnParameters,
                    IsComposable = true,
                    IsFunctionImport = true,
                    EntitySets = entitySets
                };

            this._conceptualFunction = EdmFunction.Create(
                this._name,
                model.Container.Name,
                DataSpace.CSpace,
                functionPayload,
                null);

            model.Container.AddFunctionImport(this._conceptualFunction);
        }

        private void CreateStoreFunction(EdmModel model)
        {
            var returnParameter = new[]{
                    FunctionParameter.Create(
                        Utilities.DefaultReturnValueName,
                        this._storeEntity.GetCollectionType(),
                        ParameterMode.ReturnValue)};

            var schema = model.Container.BaseEntitySets
                .Single(baseEntitySet => baseEntitySet.Name == _entity.Name)
                .Schema;

            var functionPayload =
                new EdmFunctionPayload
                {
                    Parameters = this._parameters
                        .Select(
                            p => FunctionParameter.Create(
                                p.Name,
                                p.StoreType,
                                ParameterMode.In)).ToArray(),

                    ReturnParameters = returnParameter,
                    IsComposable = true,
                    Schema = schema,
                };

            this._storeFunction = model.AddFunction(
                this._name,
                functionPayload);
        }

        private void AddFunctionImportMappingComposable(EntityContainerMapping conceptualToStoreMapping)
        {
            conceptualToStoreMapping.AddFunctionImportMapping(
                new FunctionImportMappingComposable(
                    this._conceptualFunction,
                    this._storeFunction,
                    new FunctionImportResultMapping(),
                    conceptualToStoreMapping));
        }
    }
}
